import { IpcRendererEvent, contextBridge, ipcRenderer } from 'electron';

import IpcMessageData from '../main/ipc/ipc-message-data';

// From preload we cannot import other ts file by now.
// Then we defined IpcMessage in preload and import it from main-service.
// Once preload is imported from main-service contextBridge?.exposeInMainWorld will be executed twice. The first time is even before main.
// That's why we use safe guard operator to make sure no errors since contextBridge is undefined before main function runs.
//
// https://www.electronjs.org/docs/latest/tutorial/ipc
// Pattern 1: Renderer to main (one-way)
// To fire a one-way IPC message from a renderer process to the main process, you can use the ipcRenderer.send API to send a message that is then received by the ipcMain.on API.
//
// Pattern 2: Renderer to main (two-way)
// A common application for two-way IPC is calling a main process module from your renderer process code and waiting for a result. This can be done by using ipcRenderer.invoke paired with ipcMain.handle.
contextBridge?.exposeInMainWorld('nativeAPI', {
  // One-way communications (renderer to main).
  exitApp: (): void => ipcRenderer.send(IpcMessage.exitApp),
  exec: (fileFullName: string): void => ipcRenderer.send(IpcMessage.exec, fileFullName), // Used to open file.
  execFile: (fileFullName: string): void => ipcRenderer.send(IpcMessage.execFile, fileFullName), // Used to run executable.

  // Two-way communications (renderer to main).
  download: (param: {
    url: string;
    targetDir?: string;
    targetFullName?: string;
  }): Promise<string> => ipcRenderer.invoke(IpcMessage.download, param),
  extractFileIcon: (fileFullName: string): Promise<string> =>
    ipcRenderer.invoke(IpcMessage.extractFileIcon, fileFullName),
  getAppVersion: (): Promise<string> => ipcRenderer.invoke(IpcMessage.getAppVersion),
  getSystemTempDir: (): Promise<string> => ipcRenderer.invoke(IpcMessage.getSystemTempDir),
  isDevEnv: (): Promise<boolean> => ipcRenderer.invoke(IpcMessage.isDevEnv),
  loadAssetJsonFile: (fileRelativePath: string): Promise<any | null> =>
    ipcRenderer.invoke(IpcMessage.loadAssetJsonFile, fileRelativePath),
  showOpenDirectoryDialog: (): Promise<string[] | null> =>
    ipcRenderer.invoke(IpcMessage.showOpenDirectoryDialogSync),
  showOpenFileDialog: (): Promise<string[] | undefined> =>
    ipcRenderer.invoke(IpcMessage.showOpenFileDialog),

  // Main to renderer.
  onUpdateCounter: (callback: (value: number) => void) =>
    ipcRenderer.on(IpcMessage.updateCounter, (_, value: number) => callback(value)),
  sendAndGet: (callback: (msg: string) => string) =>
    ipcRenderer.on(
      IpcMessage.sendAndGet,
      (event: IpcRendererEvent, ipcMessageData: IpcMessageData) => {
        const res = callback(ipcMessageData.payload);

        if (ipcMessageData.callbackMessageName) {
          event.sender.send(ipcMessageData.callbackMessageName, res);
        }
      },
    ),
});

export default class IpcMessage {
  static readonly download: string = 'download';
  static readonly exitApp: string = 'exit-app';
  static readonly exec: string = 'exec';
  static readonly execFile: string = 'exec-file';
  static readonly extractFileIcon: string = 'extract-file-icon';
  static readonly getAppVersion: string = 'get-app-version';
  static readonly getSystemTempDir: string = 'get-system-temp-dir';
  static readonly loadAssetJsonFile: string = 'load-asset-json-file';
  static readonly isDevEnv: string = 'is-dev-env';
  static readonly showOpenDirectoryDialogSync: string = 'show-open-directory-dialog-sync';
  static readonly showOpenFileDialog: string = 'show-open-file-dialog';
  static readonly updateCounter: string = 'update-counter';
  static readonly sendAndGet: string = 'send-and-get';
}
